#include "mycamera.h"

//封装函数把YUV转换成RGB
int yuvtorgb(int y,int u,int v)
{
    int r,g,b;
    int pix; //存放ARGB数据
    r=y+1.4075*(v-128);
    g=y-0.3455*(u-128)-0.7169*(v-128);
    b=y+1.779*(u-128);
    //判断计算结果--》控制在0--255之间
    if(r>255)
        r=255;
    if(g>255)
        g=255;
    if(b>255)
        b=255;
    if(r<0)
        r=0;
    if(g<0)
        g=0;
    if(b<0)
        b=0;
    //由于液晶屏要求是ARGB，因此我顺便把RGB转换得到ARGB
    pix=0x00<<24|r<<16|g<<8|b;
    return pix;
}

//封装函数把一帧画面的YUYV数据全部转换成ARGB数据
/*
    yuyvdata --》原始YUYV数据
    argbdata --》保存你转换得到的所有的ARGB数据
*/
int allyuyvtoargb(char *yuyvdata,int *argbdata)
{
    /*
        yuyvdata[0] -->Y1
        yuyvdata[1] -->U
        yuyvdata[2] -->Y2
        yuyvdata[3] -->V
    */
    int i,j;
    //循环总次数：(W*H)/2
    for(i=0,j=0; i<W*H; i+=2,j+=4) //i<W*H
    {
        //每一轮得到两个像素点，总共W*H个像素点
        argbdata[i]=yuvtorgb(yuyvdata[j],yuyvdata[j+1],yuyvdata[j+3]);
        argbdata[i+1]=yuvtorgb(yuyvdata[j+2],yuyvdata[j+1],yuyvdata[j+3]);
    }
    return 0;
}

mycamera::mycamera()
{

}

int mycamera::camera_init()
{
    int ret;
    int i;

    //打开液晶屏的驱动
    lcdfd=open("/dev/fb0",O_RDWR);
    if(lcdfd==-1)
    {
        perror("打开液晶屏的驱动失败了!\n");
        return -1;
    }

    //映射得到液晶屏的首地址
    lcdmem=(int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcdfd,0);
    if(lcdmem==NULL)
    {
        perror("映射液晶屏失败了!\n");
        return -1;
    }

    //打开摄像头的驱动
    camerafd=open("/dev/video7",O_RDWR);
    if(camerafd==-1)
    {
        perror("打开摄像头的驱动失败了!\n");
        return -1;
    }

    //设置摄像头的采集格式
    struct v4l2_format myfmt;
    bzero(&myfmt,sizeof(myfmt));
    myfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    myfmt.fmt.pix.width=W; //画面的宽
    myfmt.fmt.pix.height=H; //画面的高
    myfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //像素点的格式，采用YUV格式

    ret=ioctl(camerafd,VIDIOC_S_FMT,&myfmt);
    if(ret==-1)
    {
        perror("设置摄像头的采集格式失败了!\n");
        return -1;
    }

    //申请缓冲区
    struct v4l2_requestbuffers mybuf;
    bzero(&mybuf,sizeof(mybuf));
    mybuf.count=4; //缓冲区的个数(缓冲区是重复利用的)，一般申请4--8即可
    mybuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    mybuf.memory=V4L2_MEMORY_MMAP;

    ret=ioctl(camerafd,VIDIOC_REQBUFS,&mybuf);
    if(ret==-1)
    {
        perror("申请缓冲区失败了!\n");
        return -1;
    }

    //分配缓冲区-->顺便得到每个缓冲区的首地址
    //等一会你需要从缓冲区里面取出画面(本质上就是C语言的指针操作，指针的加法运算)

    for(i=0; i<4; i++)
    {
        bzero(&otherbuf,sizeof(otherbuf));
        otherbuf.index=i; //缓冲区的索引号，从0开始
        otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
        otherbuf.memory=V4L2_MEMORY_MMAP;
        ret=ioctl(camerafd,VIDIOC_QUERYBUF,&otherbuf);
        if(ret==-1)
        {
            perror("分配缓冲区失败了!\n");
            return -1;
        }
        //顺便映射得到每个缓冲区的首地址
        array[i].len=otherbuf.length; //存大小
        array[i].start=mmap(NULL,otherbuf.length,PROT_READ|PROT_WRITE,MAP_SHARED,camerafd,otherbuf.m.offset);
        if(array[i].start==NULL)
        {
            perror("映射缓冲区首地址失败了!\n");
            return -1;
        }
        //申请摄像头画面入队--》把摄像头拍摄的画面存放到缓冲区
        ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
        if(ret==-1)
        {
            perror("申请入队失败了!\n");
            return -1;
        }
    }

    //启动摄像头开始采集画面
    mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret=ioctl(camerafd,VIDIOC_STREAMON,&mytype);
    if(ret==-1)
    {
        perror("启动摄像头采集画面失败了!\n");
        return -1;
    }
    return 0;
}

int mycamera::camera_capture()
{
    int i,j;
    int ret;
    //自己定义一个数组，存放转换得到的一帧画面的ARGB数据
    int argbbuf[W*H];
    fd_set myset;
    FD_ZERO(&myset);
    FD_SET(camerafd,&myset);

    //循环出队，入队显示视频流
    for(i=0; i<4; i++)
    {
        //监测摄像头是否有数据可读
        ret=select(camerafd+1,&myset,NULL,NULL,NULL);
        if(ret>0) //说明摄像头有数据可读-->画面有了
        {
            bzero(&otherbuf,sizeof(otherbuf));
            otherbuf.index=i; //缓冲区的索引号，从0开始
            otherbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
            otherbuf.memory=V4L2_MEMORY_MMAP;
            //先出队
            ret=ioctl(camerafd,VIDIOC_DQBUF,&otherbuf);
            if(ret==-1)
            {
                perror("申请出队失败了!\n");
                return -1;
            }
            //把当前出队的画面数据在液晶屏上显示出来
            allyuyvtoargb((char *)(array[i].start),argbbuf);
            //把ARGB数据填充到开发板的液晶屏
            /*
                第一行：argbbuf[0*W]--argbbuf[W-1]
                        lcdmem+0*800
                第二行：argbbuf[1*W]--argbbuf[2*W-1]
                        lcdmem+1*800
                第三行：argbbuf[2*W]--argbbuf[3*W-1]
                        lcdmem+2*800
            */
            for(j=0; j<H; j++)
                memcpy(lcdmem+80+j*800,&argbbuf[j*W],W*4);

            //再入队
            ret=ioctl(camerafd,VIDIOC_QBUF,&otherbuf);
            if(ret==-1)
            {
                perror("申请入队失败了!\n");
                return -1;
            }
        }
    }
    return 0;
}

int mycamera::camera_uninit()
{
    int i,ret;
    //关闭摄像头
    ret=ioctl(camerafd,VIDIOC_STREAMOFF,&mytype);
    if(ret==-1)
    {
        perror("关闭摄像头采集画面失败了!\n");
        return -1;
    }
    munmap(lcdmem,800*480*4);
    for(i=0; i<4; i++)
        munmap(array[i].start,array[i].len);

    close(camerafd);
    close(lcdfd);
    return 0;
}
